package requetes;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;

import modele.Noeud;
import modele.Terme;
import parsage.Lemmatiseur;
import application.Parametres;
import bdd.GetBDD;

public class LancerRequete {

	/**
	 * construit la liste des requetes a partir du xml
	 * 
	 * @param suffixeXml
	 */
	public static ArrayList<String> parseXmlRequeteToJava(String suffixeXml) {
		ArrayList<String> listeRequetes = new ArrayList<String>();
		// on ne lit que les balises "text"

		return listeRequetes;
	}

	/**
	 * 
	 * @param requete
	 *            string (requete a decouper en mots)
	 * @return arraylist de termes (Terme), calcules en lemmatisant les mots de
	 *         la requette puis en selectionnant uniquement s'ils appartiennent
	 *         a la base : vecteur de la requete
	 */
	public static ListeTermesEtPoids getTermesRequete(String requete,
			boolean requetePonderee, boolean isLabel) {
		ArrayList<String> vecteurRequete = Lemmatiseur.decoupeChaine(requete,
				isLabel);
		ArrayList<Terme> listeTermes = new ArrayList<Terme>();
		ArrayList<Double> listePoids = new ArrayList<Double>();
		GetBDD getBdd = GetBDD.getInstance();

		// Deux gestions des poids de requete possibles :
		// - Soit chaque mot de la requete a le meme poids, et seul le score du
		// noeud pour chaque terme associe influencera le score final
		// - Soit on pondere les mots de maniere decroissante : pour une requete
		// de N mots, le premier mot a un poids de N, le suivant N-1, etc
		// jusqu'au dernier pesant 1. L'avantage de cette solution est de donner
		// la priorite aux termes associes aux premiers mots de la requete, et
		// donc aux noeuds contenant ces termes
		int poids = 1;
		if (requetePonderee) {
			poids = vecteurRequete.size();
		}
		for (String mot : vecteurRequete) {
			Terme terme = getBdd.recupererTermeFromString(mot);
			if (terme != null) {
				if (!listeTermes.contains(terme)) {
					listeTermes.add(terme);
					listePoids.add((double) poids);
				} else {
					int index = listeTermes.indexOf(terme);
					listePoids.set(index, listePoids.get(index) + poids);
				}
			}
			if (requetePonderee) {
				poids--;
			}
		}
		return new ListeTermesEtPoids(listeTermes, listePoids);
	}

	/**
	 * 
	 * @param vecteurRequete
	 *            vecteur de la requete
	 * @param idNoeud
	 *            noeud
	 * @return vecteur de scores final pour un noeud
	 */
	public static ArrayList<Double> getScoresParNoeudAPartirRequete(
			ArrayList<Terme> vecteurRequete, int idNoeud) {
		ArrayList<Double> scoresNoeud = new ArrayList<Double>();
		for (Terme terme : vecteurRequete) {
			double score = terme.getScorePourNoeud(idNoeud);
			scoresNoeud.add(score);
		}

		// on peut maintenant calculer le nouveau score tenant compte de la
		// position
		scoresNoeud = calculerNouveauScoreGraceAPosition(scoresNoeud,
				vecteurRequete, idNoeud);
		return scoresNoeud;
	}

	/**
	 * 
	 * @param scoresNoeud
	 *            vecteur de score de base
	 * @param vecteurRequete
	 *            arraylist de termes de la requete
	 * @param idNoeud
	 *            noeud
	 * @return le vecteur modifie de scores pour prendre en compte la position
	 *         (probleme de "pomme de terre")
	 */
	private static ArrayList<Double> calculerNouveauScoreGraceAPosition(
			ArrayList<Double> scoresNoeud, ArrayList<Terme> vecteurRequete,
			int idNoeud) {

		for (int iTerme = 0; iTerme < vecteurRequete.size() - 1; iTerme++) {
			Terme terme1 = vecteurRequete.get(iTerme);
			Terme terme2 = vecteurRequete.get(iTerme + 1);
			ArrayList<Integer> listePos1 = terme1
					.getPositionsDansNoeud(idNoeud);
			ArrayList<Integer> listePos2 = terme2
					.getPositionsDansNoeud(idNoeud);
			for (int pos1 : listePos1) {
				if (listePos2.contains(pos1 + 1)) {
					// System.out
					// .println("Deux termes consecutifs trouves dans un noeud : \""
					// + terme1.getTerme()
					// + "\" et \""
					// + terme2.getTerme()
					// + "\" dans le noeud "
					// + idNoeud);
					scoresNoeud.set(iTerme, scoresNoeud.get(iTerme) * 2);
					scoresNoeud
							.set(iTerme + 1, scoresNoeud.get(iTerme + 1) * 2);
					// System.out.println("    Nouveaux poids : "
					// + scoresNoeud.get(iTerme) + " et "
					// + scoresNoeud.get(iTerme + 1));
				}
			}
		}

		return scoresNoeud;
	}

	/**
	 * Retourne les noeuds les plus pertinents pour la requete
	 * 
	 * @param requete
	 *            sous forme d'ArrayList de String (=mot)
	 * @param seuilPertinence
	 *            : nombre de resultats retournes ou tous les resultats si
	 *            seuilPertinence = 0
	 * @return les noeuds pertinents et leurs scores
	 * @throws Exception
	 *             si lors d'un appel a cos, les deux entrees ne sont pas de
	 *             meme longueur
	 */
	public static ResultatRequete resultatRequete(String requete,
			int seuilPertinence) throws Exception {
		ArrayList<Noeud> noeudsPertinents = new ArrayList<Noeud>();
		// Pour l'instant, on ne considere pas les mots comme des labels
		// ("aPourDistance" a decouper en 3 mots "a pour distance")
		ListeTermesEtPoids listeTermesEtPoids = getTermesRequete(requete,
				Parametres.REQUETE_PONDEREE, false);
		ArrayList<Terme> vecteurRequete = listeTermesEtPoids.getListeTermes();
		ArrayList<Double> vecteurPoids = listeTermesEtPoids.getListePoids();
		ArrayList<ArrayList<Double>> vecteursScores = new ArrayList<ArrayList<Double>>();
		ArrayList<Double> vecteurCosinus = new ArrayList<Double>();
		boolean triModifie = true; // boolean pour tri a bulles

		System.out.println("Liste des termes de la requete :");
		for (int t = 0; t < vecteurRequete.size(); t++) {
			Terme terme = vecteurRequete.get(t);
			double poids = vecteurPoids.get(t);
			System.out.println(" -> \"" + terme.getTerme() + "\" de poids "
					+ poids + " dans la requete");
		}

		// recuperer tous les noeuds dans noeudsPertinents
		noeudsPertinents = getNoeudRequete(vecteurRequete);
		System.out.println("Lors de la requete, " + noeudsPertinents.size()
				+ " noeuds pertinents ont ete trouves");

		// boucle sur les noeuds et rempli vecteursScores avec les resultats de
		// getScoresParNoeudAPartirRequete
		System.out
				.println("Calcul du score de chaque noeud par rapport a chaque terme de la requete");
		for (int i = 0; i < noeudsPertinents.size(); i++) {
			vecteursScores.add(getScoresParNoeudAPartirRequete(vecteurRequete,
					noeudsPertinents.get(i).getIdNoeud()));
		}

		System.out
				.println("Calcul du score de chaque noeud par rapport a la requete");

		// calculer le produit scalaire (avec la formule du cosinus) pour
		// chaque noeud
		for (int i = 0; i < noeudsPertinents.size(); i++) {
			if (Parametres.UTILISE_COS) {
				vecteurCosinus.add(cos(vecteursScores.get(i), vecteurPoids));
			} else {
				vecteurCosinus
						.add(scalaire(vecteursScores.get(i), vecteurPoids));
			}
		}

		System.out
				.println("Les scores sont calcules pour chaque noeud, il faut trier la liste dans l'ordre des scores decroissants");

		// classer ces resultats par ordre decroissant
		// les associer aux noeuds pour pouvoir les renvoyer
		while (triModifie) {
			triModifie = false;
			for (int j = 0; j < noeudsPertinents.size() - 1; j++) {
				// si deux noeuds consecutifs ne sont pas dans le bon ordre on
				// les swap
				if (vecteurCosinus.get(j) < vecteurCosinus.get(j + 1)) {
					// swapDouble(vecteurCosinus, j, j+1);
					Collections.swap(vecteurCosinus, j, j + 1);
					// swapNoeud(noeudsPertinents, j, j+1);
					Collections.swap(noeudsPertinents, j, j + 1);
					triModifie = true;
				}
			}
		}

		System.out
				.println("On a trie la liste des noeuds pertinents, on la tronque");

		// si un seuil est defini :
		if (seuilPertinence > 0) {
			// et ne garder que les pertinents
			int nbASupprimer = noeudsPertinents.size() - seuilPertinence;
			for (int i = 0; i < nbASupprimer; i++) {
				noeudsPertinents.remove(seuilPertinence);
				vecteurCosinus.remove(seuilPertinence);
			}
		}
		System.out.println("Apres troncature, elle contient "
				+ noeudsPertinents.size() + " noeuds");
		return new ResultatRequete(noeudsPertinents, vecteurCosinus);
	}

	/**
	 * Methode demandant a l'utilisateur de taper une requete, et affichant les
	 * resultats sous forme de XPath
	 * 
	 * @param nbNoeuds
	 *            : nombre de resultats retournes ou tous les resultats si
	 *            nbNoeuds = 0
	 * @return les resultats sous forme d'une liste de XPath stringifies
	 */
	public static ArrayList<String> appli(int nbNoeuds) {
		Scanner saisieUtilisateur = new Scanner(System.in);
		String requete = "";
		ResultatRequete noeuds;
		GetBDD bdd = GetBDD.getInstance();
		ArrayList<String> results = new ArrayList<String>();

		// demande a l'utilisateur de taper une requete
		System.out.println("Tapez votre requete.");
		// Remarque : tu avais mis hasNext et next et du coup ca prenait que le
		// premier mot
		if (saisieUtilisateur.hasNextLine()) {
			requete = saisieUtilisateur.nextLine();
			try {
				System.out.println("Texte de la requete : " + requete);
				noeuds = resultatRequete(requete, nbNoeuds);
				System.out.println("\nResultats de la requete : \n");
				// for (int i = 0; i < noeuds.getListeNoeuds().size(); i++) {
				// System.out.println(noeuds.getListeNoeuds().get(i)
				// .getxPath()
				// + " ---> " + noeuds.getListeScores().get(i));
				// }

				// Afficher les xpath des noeuds pertinents
				for (Noeud noeud : noeuds.getListeNoeuds()) {
					System.out.println("Collection/"
							+ bdd.recupererDocsFromId(noeud.getIdDocument())
							+ "\t" + noeud.getxPath() + " ---> Noeud "
							+ noeud.getIdNoeud());

					results.add("Collection/"
							+ bdd.recupererDocsFromId(noeud.getIdDocument())
							+ "\t" + noeud.getxPath());
				}

			} catch (Exception e) {
				System.err.println("Echec de la requete");
				e.printStackTrace();
			}
		}
		saisieUtilisateur.close();
		return results;
	}

	/**
	 * Methode traitant une requete utilisateur, et retournant les noeuds
	 * trouves
	 * 
	 * @param nbNoeuds
	 *            : nombre de resultats retournes ou tous les resultats si
	 *            nbNoeuds = 0
	 * @param requete
	 *            a traiter
	 * @return les resultats sous forme d'une liste de XPath stringifies
	 */
	public static ArrayList<String> traiterRequete(int nbNoeuds, String requete) {
		ResultatRequete noeuds;
		GetBDD bdd = GetBDD.getInstance();
		ArrayList<String> results = new ArrayList<String>();
		try {
			noeuds = resultatRequete(requete, nbNoeuds);

			for (Noeud noeud : noeuds.getListeNoeuds()) {
				results.add("Collection/"
						+ bdd.recupererDocsFromId(noeud.getIdDocument()) + "\t"
						+ noeud.getxPath());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return results;
	}

	/**
	 * Recupere tous les noeuds qui ont au moins un terme de la requete
	 * 
	 * @param vecteurRequete
	 *            : les termes de la requete
	 * @return la liste des noeuds
	 */
	public static ArrayList<Noeud> getNoeudRequete(
			ArrayList<Terme> vecteurRequete) {
		ArrayList<Noeud> noeuds = new ArrayList<Noeud>();
		ArrayList<Noeud> noeudsInterm;
		GetBDD bdd = GetBDD.getInstance();

		for (int i = 0; i < vecteurRequete.size(); i++) {
			noeudsInterm = bdd.getNoeudsFromTerme(vecteurRequete.get(i));
			for (int j = 0; j < noeudsInterm.size(); j++) {
				if (!noeuds.contains(noeudsInterm.get(j)))
					noeuds.add(noeudsInterm.get(j));
			}
		}
		return noeuds;
	}

	/**
	 * Calcule un produit scalaire simple entre deux vecteurs
	 * 
	 * @param v1
	 *            : vecteur de score du noeud
	 * @return le produit scalaire
	 * @throws Exception
	 *             si les deux vecteurs en entree n'ont pas la meme longueur
	 */
	public static double scalaire(ArrayList<Double> v1, ArrayList<Double> v2)
			throws Exception {
		double result = 0;

		if (v1.size() != v2.size()) {
			throw new Exception(
					"Les deux vecteurs en entree de LancerRequete.scalaire() n'ont pas la meme longueur");
		}

		for (int i = 0; i < v1.size(); i++) {
			result += (v1.get(i) * v2.get(i));
		}

		return result;
	}

	/**
	 * Calcule le cosinus entre le vecteurs de Scores d'un Noeud et le vecteur
	 * de la requete
	 * 
	 * @param vecteurScoresNoeud
	 *            : vecteur de score du noeud
	 * @return cosinus
	 * @throws Exception
	 *             si les deux vecteurs en entree n'ont pas la meme longueur
	 */
	public static double cos(ArrayList<Double> vecteurScoresNoeud,
			ArrayList<Double> vecteurPoidsRequete) throws Exception {
		double result = 0;
		double numerateur = 0;
		double sommeYi = 0;
		double sommeXi = 0;
		double denominateur = 0;

		if (vecteurScoresNoeud.size() != vecteurPoidsRequete.size()) {
			throw new Exception(
					"Les deux vecteurs en entree de cos n'ont pas la meme longueur");
		}

		for (int i = 0; i < vecteurScoresNoeud.size(); i++) {
			double xi = vecteurScoresNoeud.get(i);
			double yi = vecteurPoidsRequete.get(i);
			numerateur += (xi * yi);
			// xi * xi est des milliers de fois plus rapide que Math.pow(xi, 2)
			// (operation sur des types basiques V/S appel de fonction
			// statique). On ne se sert de Math.pow que pour des puissances
			// elevees qui obligeraient a ecrire une boucle for pour calculer la
			// valeur, ce qui surchargerait le code. Mais pour mettre au carre,
			// on peut s'en passer
			sommeXi += (xi * xi);
			sommeYi += (yi * yi);
		}

		denominateur = Math.sqrt(sommeXi * sommeYi);

		result = numerateur / denominateur;

		return result;
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		ArrayList<String> nosResultats = LancerRequete.appli(5); // parametre =
																	// nb
		// resultats renvoyes
		Evaluation eval = ResultatRequete.evaluer(nosResultats,
				"ressources/Qrels/qrel03.txt");

		System.out.println("################## RESULTATS ##################");
		System.out.println("Rappel = " + eval.getRappel());
		System.out.println("Precision = " + eval.getPrecision());

		System.out.println("resultats communs");
		for (int i = 0; i < eval.getCommonResults().size(); i++) {
			System.out.println(eval.getCommonResults().get(i));
		}
		GetBDD.getInstance().fermeConnexion();
	}

}
